iT邦幫忙

2024 iThome 鐵人賽

DAY 29
0
Software Development

可以Go一輩子嗎?系列 第 29

專案練習: 字幕搜尋API (3/4)

  • 分享至 

  • xImage
  •  

專案練習: 字幕搜尋API (3/4)

今天要把之前的功能串接至API,進而透過API來擷取指定frame的圖片或GIF,首先我們要先定義API的request與response格式,接著再來實作API的handler

專案資料夾結構

mygo
├── backend
│   ├── backend.go
│   ├── go.mod
│   ├── go.sum
│   ├── payload.go
│   └── route.go
├── data
│   ├── data.go
│   ├── db.go
│   ├── go.mod
│   └── go.sum
├── data.json
├── go.mod
├── go.sum
├── main.go
├── mygo
├── README.md
├── static
│   ├── index.css
│   └── index.js
├── templates
│   └── index.html
└── video
    ├── go.mod
    ├── go.sum
    └── video.go
  • backend.go為API的初始化設定、指定route中的handler,並mapping到指定的uri
  • payload.go為API的request與response格式,以標記JSON Tag的struct來定義
  • route.go為API的handler

API Payload

/api/search

  • Request
{
  "query": "string",
  "episode": "string",
  "nth_page": 0,
  "paged_by": 0
}
  • Response
{
  "results": [
    {
      "episode": "string",
      "frame_start": 0,
      "frame_end": 0,
      "text": "string",
      "segment_id": 0
    }
  ]
}

/api/extract_frame

  • Request
{
  "episode": "string",
  "frame": 0
}
  • Response
{
  "image": "string" // base64 encoded image
}

/api/extract_gif

  • Request
{
  "episode": "string",
  "frame": 0
}
  • Response
{
  "gif": "string" // base64 encoded image
}

確定架構後就可以開始實作API了

實作

定義API的request與response格式

  • mygo/backend/payload.go
package backend

import (
	"mygo/data"
)

// request of /api/search
type SearchRequest struct {
	Query   string `json:"query"`
	Episode string `json:"episode"`
	NthPage int    `json:"nth_page"`
	PagedBy int    `json:"paged_by"`
}

// response of /api/search
type SearchResponse struct {
	Count   int                 `json:"count"`
	Results []data.SentenceItem `json:"results"`
}

// request of /api/extract_frame
type ExtraceFrameRequest struct {
	Episode     string `json:"episode"`
	FrameNumber int    `json:"frame"`
}

// response of /api/extract_frame
type ExtraceFrameResponse struct {
	Frame string `json:"frame"`
}

// request of /api/extract_gif
type ExtraceGIFRequest struct {
	Episode string `json:"episode"`
	Start   int    `json:"start"`
	End     int    `json:"end"`
}

// response of /api/extract_gif
type ExtraceGIFResponse struct {
	GIF string `json:"gif"`
}

field的名稱其實沒差,只要payload符合JSON tag的名稱,還有類型符合就可以了

實作API的handler

  • mygo/backend/route.go
package backend

import (
	"encoding/base64"
	"fmt"
	"mygo/data"
	"mygo/video"
	"net/http"
	"os"
	"runtime"

	"github.com/gin-gonic/gin"
)

const (
	pagedBy   = 20
	videoPath = "%s/mygo-anime/%s.mp4" // $HOME/mygo-anime/%$episode.mp4
)

var homePath = os.Getenv("HOME")

func Search(c *gin.Context) {
	var req SearchRequest
	if err := c.ShouldBindBodyWithJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if req.PagedBy == 0 {
		req.PagedBy = pagedBy
	}

	result, count, err := data.SearchByText(req.Query, req.Episode, req.PagedBy, req.NthPage)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, SearchResponse{Results: result, Count: int(count)})
}

func ExtractFrame(c *gin.Context) {
	var req ExtraceFrameRequest
	if err := c.ShouldBindBodyWithJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if runtime.GOOS == "windows" {
		homePath = os.Getenv("USERPROFILE")
	}
	videoPath := fmt.Sprintf(videoPath, homePath, req.Episode)
	frame, fps := video.FetchVideoFPS(videoPath)
	if frame < req.FrameNumber {
		c.JSON(http.StatusBadRequest, gin.H{"error": "frame number out of range"})
		return
	}
	buf, err := video.ExtractFrame(req.Episode, req.FrameNumber, fps)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, ExtraceFrameResponse{Frame: base64.StdEncoding.EncodeToString(buf.Bytes())})
}

func ExtractGIF(c *gin.Context) {
	var req ExtraceGIFRequest
	if err := c.ShouldBindBodyWithJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if runtime.GOOS == "windows" {
		homePath = os.Getenv("USERPROFILE")
	}
	videoPath := fmt.Sprintf(videoPath, homePath, req.Episode)
	frame, fps := video.FetchVideoFPS(videoPath)
	if req.Start < 0 || req.End < 0 || req.End > frame {
		c.JSON(http.StatusBadRequest, gin.H{"error": "invalid start or end frame"})
		return
	}
	buf, err := video.ExtractGIF(req.Episode, req.Start, req.End, fps)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, ExtraceGIFResponse{GIF: base64.StdEncoding.EncodeToString(buf.Bytes())})
}

Handler講解

  • Search handler
    • 透過ShouldBindBodyWithJSON將request的body bind到SearchRequest struct中
    • 呼叫data.SearchByText來取得搜尋結果
    • 將結果回傳至client端(這邊我們只回傳前pagedBy筆資料)
  • ExtractFrame handler
    • 透過ShouldBindBodyWithJSON將request的body bind到ExtraceFrameRequest struct中
    • 呼叫video.FetchVideoFPS來取得影片的fps
    • 呼叫video.ExtractFrame來取得指定frame的圖片
    • 將圖片回傳至client端(這邊回傳base64編碼後的圖片)
  • ExtractGIF handler
    • 透過ShouldBindBodyWithJSON將request的body bind到ExtraceGIFRequest struct中
    • 呼叫video.FetchVideoFPS來取得影片的fps
    • 呼叫video.ExtractGIF來取得指定frame範圍的GIF
    • 將GIF回傳至client端(這邊回傳base64編碼後的GIF)

綁定handler到engine

  • mygo/backend/backend.go
package backend

import (
	"github.com/gin-gonic/gin"
)

func CreateEngine() *gin.Engine {
	var r = gin.Default()
	r.POST("/api/search", Search)
	r.POST("/api/extract_frame", ExtractFrame)
	r.POST("/api/extract_gif", ExtractGIF)

	return r
}

在main.go中啟動API

  • mygo/main.go
package main

import (
    "mygo/backend"
)

func main() {
    r := backend.CreateEngine()
    r.Run(":8080")
}

最後透過go run main.go或是go build後執行./mygo來啟動API,接著就可以透過Postman或是其他工具來測試API了
Image

  • Frame Result
    Haruhikage

  • GIF Response
    gif


那麼今天的文章就到這告一段落,如果我的文章有任何地方有錯誤請在留言區反應
明天就是最後一天了,我們一起加油吧!
Image
time

REF


上一篇
專案練習: 字幕搜尋API (2/4)
下一篇
專案練習: 字幕搜尋API (END)
系列文
可以Go一輩子嗎?31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
c8763yee
iT邦新手 3 級 ‧ 2024-10-07 01:09:34

想玩玩看的可以到這個連結來嘗試

我要留言

立即登入留言